/**
 * Title: MsgVerificationServiceImpl.java
 * Description: Copyright: Copyright (c) 2019 Company: BHFAE
 *
 * @author Sharp
 * @date 2019-7-31 21:57
 * @description Project Name: Grote
 * @Package: com.srct.service.service.impl
 */
package com.sharp.authorization.service.sms.impl;

import com.sharp.authorization.config.msg.IMsgVerification;
import com.sharp.authorization.constants.err.AccountErrCode;
import com.sharp.authorization.constants.param.SmsParam;
import com.sharp.authorization.service.sms.MsgVerificationService;
import com.sharp.authorization.service.token.TokenService;
import com.sharp.authorization.vo.msg.MsgVerificationReq;
import com.sharp.kernel.constant.err.ErrCodeSys;
import com.sharp.framework.service.cache.FrameCacheService;
import com.sharp.framework.utils.security.RandomUtil;
import com.sharp.kernel.validate.Validator;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class MsgVerificationServiceImpl implements MsgVerificationService {

    private static final Map<String, MsgCodeType> MSG_CODE_TYPE_MAP = new ConcurrentHashMap<>(16);

    private static final String INTERNAL_TIMESTAMP = "_INTERNAL_TIMESTAMP";
    private static final String EXPIRED_TIMESTAMP = "_EXPIRED_TIMESTAMP";
    private static final String PHONE_NUMBER = "_PHONE_NUMBER";

    @Resource
    private TokenService tokenService;

    @Resource
    private FrameCacheService frameCacheService;

    @Override
    public IMsgVerification getMsgCodeType(String msgCodeType) {
        return MSG_CODE_TYPE_MAP.getOrDefault(msgCodeType, null);
    }

    @Override
    public String generateMsgCode(String phoneNumber, IMsgVerification msgCodeType) {
        String code = RandomUtil.createRandomNum(msgCodeType.getLength());
        String token = tokenService.getToken();
        Validator.assertNotBlank(token, ErrCodeSys.PA_DATA_NOT_EXIST, "token");

        String internalTimeStamp = tokenService.getItem(token, getInternalTimestampTokenItem(msgCodeType.name()), String.class);
        if (StringUtils.isNotBlank(internalTimeStamp)) {
            Validator.assertTrue(Long.parseLong(internalTimeStamp) <= System.currentTimeMillis(), AccountErrCode.AC_MSG_CODE_INTERNAL);
        }
        fillMsgCodeToken(token, msgCodeType.name(), phoneNumber, code, msgCodeType.getInternal(), msgCodeType.getTimeout());
        return code;
    }

    private String getInternalTimestampTokenItem(String type) {
        return type + INTERNAL_TIMESTAMP;
    }

    private void fillMsgCodeToken(String token, String type, String phoneNumber, String code, long internalTimestamp, long expiredTimestamp) {
        Map<String, Object> map = tokenService.getTokenValue();
        map.put(type, code);
        map.put(getPhoneNumberTokenItem(type), phoneNumber);
        map.put(getInternalTimestampTokenItem(type), String.valueOf(System.currentTimeMillis() + internalTimestamp * 1000));
        map.put(getExpiredTimestampTokenItem(type), String.valueOf(System.currentTimeMillis() + expiredTimestamp * 1000));
        tokenService.setTokenValue(token, map);
    }

    private String getPhoneNumberTokenItem(String type) {
        return type + PHONE_NUMBER;
    }

    private String getExpiredTimestampTokenItem(String type) {
        return type + EXPIRED_TIMESTAMP;
    }

    @Override
    public void validateMsgCode(String token, MsgVerificationReq req, String msgCodeType) {
        validateMsgCode(token, req.getPhoneNumber(), req.getMsgCode(), msgCodeType);
    }

    @Override
    public void validateMsgCode(String token, String phoneNumber, String code, String msgCodeType) {
        Validator.assertNotBlank(token, ErrCodeSys.PA_DATA_NOT_EXIST, "token");
        Validator.assertNotBlank(code, ErrCodeSys.PA_DATA_NOT_EXIST, "短信验证码");
        String msgPhoneNumber = tokenService.getItem(token, getPhoneNumberTokenItem(msgCodeType), String.class);
        Validator.assertNotNull(msgPhoneNumber, AccountErrCode.AC_NO_GET_MSG_CODE);
        Validator.assertTrue(StringUtils.equals(phoneNumber, msgPhoneNumber), ErrCodeSys.PA_DATA_DIFF, "手机号码");
        String msgCode = tokenService.getItem(token, msgCodeType, String.class);
        Long timeStamp = tokenService.getItem(token, getExpiredTimestampTokenItem(msgCodeType), Long.class);
        Validator.assertFalse(timeStamp <= System.currentTimeMillis(), AccountErrCode.AC_MSG_CODE_EXPIRED);
        if (!frameCacheService.getParamSwitch(SmsParam.TEST_MODE_SMS_SEND_SWITCH)) {
            Validator.assertMatch(msgCode, code.toLowerCase(), AccountErrCode.AC_MSG_CODE_ERROR);
        }
        cleanMsgCodeToken(token, msgCodeType);
    }

    private void cleanMsgCodeToken(String token, String type) {
        tokenService.removeItem(token, type, getPhoneNumberTokenItem(type), getInternalTimestampTokenItem(type), getExpiredTimestampTokenItem(type));
    }

    @PostConstruct
    public void init() {
        Reflections reflections = new Reflections("com.sharp");
        Set<Class<? extends IMsgVerification>> msgVerificationClass = reflections.getSubTypesOf(IMsgVerification.class);
        for (Class<? extends IMsgVerification> clazz : msgVerificationClass) {
            if (Enum.class.isAssignableFrom(clazz)) {
                for (IMsgVerification enumItem : clazz.getEnumConstants()) {
                    if (StringUtils.isNotBlank(enumItem.name())) {
                        MSG_CODE_TYPE_MAP.put(enumItem.name(),
                                new MsgCodeType(enumItem.name(), enumItem.getInternal(), enumItem.getTimeout(), enumItem.getLength()));
                    }
                }
            }
        }
    }

    @Data
    @AllArgsConstructor
    public static class MsgCodeType implements IMsgVerification {
        private String name;
        private int internal;
        private int timeout;
        private int length;

        @Override
        public String name() {
            return this.name;
        }
    }
}
